// {{{ docs <-- this is a VIM (text editor) text fold

/**
 * DOM Tooltip 0.5.4
 *
 * Summary: Allows developers to add custom tooltips to the webpages.  Tooltips
 *		  are controlled through three style class definitions.  This library
 *		  also detects collisions against native widgets in the browser that
 *		  cannot handle the zIndex property.  But this library is even more than
 *		  that...with all the features it has, it has the potential to replace
 *		  the need for popups entirely as it can embed just about any html inside
 *		  the tooltip, leading to the possibility of having whole forms or iframes
 *		  right inside the tip...even other programs!!!
 *
 * Maintainer: Dan Allen <dan@mojavelinux.com>
 *
 * License: LGPL - however, if you use this library, please post to my forum where you
 *		  use it so that I get a chance to see my baby in action.  If you are doing
 *		  this for commercial work perhaps you could send me a few Starbucks Coffee
 *		  gift dollars to encourage future developement (NOT REQUIRED).  E-mail me
 *		  for and address.
 *
 * Homepage: http://www.mojavelinux.com/forum/viewtopic.php?t=127
 *
 * Freshmeat Project: http://freshmeat.net/projects/domtt/?topic_id=92
 *
 * Updated: 2003/02/05
 *
 * Supported Browsers: Mozilla (Gecko), IE 5.5+, Konqueror, Opera 7
 *
 * Usage: All this is required is to put the function call in the event tag
 *		for an html element. The status option (for changing the status bar
 *		text) is only available through all events, but when used with 'onmouseover'
 *		you have to return true so that the browser does not display the link text
 *		in the status bar.  To do this, wrap the domTT_activate call in the function
 *		domTT_true(), which will just return true, and then prefix it with a 'return'
 *
 * Example: <a href="index.html" onmouseover="return domTT_true(domTT_activate(this, event, 'caption', 'Help', 'content', 'This is a link with a tooltip', 'status', 'Link'));">click me</a>
 *
 * Options: Each option is followed by the value for that option.  The variable event
 *		  must be the first parameter, as shown above.  The options avaiable are:
 *			predefined (optional, must be first item if used, loads default values)
 *			caption (optional)
 *			content (required)
 *			closeLink (optional, defaults to domTT_closeLink global setting variable)
 *			status (optional, if used with mouseover must wrap call in 'return domTT_true()')
 *			type (optional, defaults to 'greasy' but can be 'sticky' or 'velcro')
 *			prefix (optional, defaults to 'domTT', for changing style class)
 *			delay (optional, defaults to global delay value domTT_activateDelay)
 *			parent (optional, defaults to document.body)
 *			onClose (optional, defaults to global domTT_onClose, either 'hide' or 'remove')
 *			id (optional, for multiple tooltips on one event trigger, such as onload)
**/

// }}}
// {{{ Settings (editable)

/**
 * Settings (editable)
 */
var domTT_offsetX = 0;
var domTT_offsetY = 2;
var domTT_direction = 'southeast';
var domTT_mouseHeight = 20;
var domTT_closeLink = 'X';
var domTT_screenEdgePadding = 5;
var domTT_activateDelay = 500;
var domTT_maxWidth = 300;
var domTT_dragStickyTips = true;
var domTT_useGlobalMousePosition = true;
var domTT_prefix = 'domTT';
var domTT_fade = 'neither';
var domTT_lifetime = 0;
var domTT_grid = 0;
var domTT_onClose = 'hide';

// }}}
// {{{ Global constants

/**
 * Global constants (DO NOT EDIT)
 */
var domTT_userAgent = navigator.userAgent.toLowerCase();
var domTT_isOpera = domTT_userAgent.indexOf('opera 7') != -1 ? 1 : 0;
var domTT_isKonq = domTT_userAgent.indexOf('konq') != -1 ? 1 : 0;
var domTT_isIE = !domTT_isKonq && !domTT_isOpera && domTT_userAgent.indexOf('msie 5.0') == -1 && document.all ? 1 : 0;
var domTT_isIE5 = domTT_isIE && domTT_userAgent.indexOf('msie 5.') != -1;
var domTT_isGecko = domTT_userAgent.indexOf('gecko') != -1 ? 1 : 0;
var domTT_useLibrary = domTT_isOpera || domTT_isKonq || domTT_isIE || domTT_isGecko ? 1 : 0;
var domTT_autoId = 1;
var domTT_zIndex = 100;
var domTT_scrollbarWidth = 14;
var domTT_hidePosition = '-1000px';
var domTT_cssFloat = domTT_isIE ? 'float' : 'cssFloat';
var domTT_eventTarget = domTT_isIE ? 'srcElement' : 'currentTarget';
var domTT_eventButton = domTT_isIE ? 'button' : 'which';
var domTT_eventTo = domTT_isIE ? 'toElement' : 'relatedTarget';
var domTT_styleOpacity = domTT_isIE ? 'filter' : 'MozOpacity';
var domTT_stylePointer = domTT_isIE ? 'hand' : 'pointer';
// ** bug in Opera that it can't set maxWidth to 'none' **
var domTT_styleNoMaxWidth = domTT_isOpera ? '10000px' : 'none';
var domTT_predefined = new domTT_Hash();
var domTT_selectElements;
var domTT_timeoutState = new domTT_Hash();
var domTT_activateTimeouts = new domTT_Hash();
var domTT_currentMousePosition = new domTT_Hash();
var domTT_dragMouseDown;
var domTT_dragOffsetLeft;
var domTT_dragOffsetTop;
var domTT_currentDragTarget;
var domTT_fadeInterval = domTT_isIE ? 10 : 40;
var domTT_fadeTimeouts = new domTT_Hash();

// }}}
// {{{ Global onmousemove

if (domTT_useLibrary && domTT_useGlobalMousePosition) {
	document.onmousemove = function(in_event) {
		var eventObj = domTT_isIE ? event : in_event;
		domTT_currentMousePosition = domTT_getEventPosition(eventObj);
		domTT_dragUpdate(in_event);
	}
}

// }}}
// {{{ class domTT_Hash()

function domTT_Hash()
{
	this.length = 0;
	this.items = new Array();
	for (var i = 0; i < arguments.length; i += 2)
	{
		if (typeof(arguments[i + 1]) != 'undefined')
		{
			this.items[arguments[i]] = arguments[i + 1];
			this.length++;
		}
	}

	this.get = function(in_key)
	{
		return this.items[in_key];
	}

	this.remove = function(in_key)
	{
		var tmp_value;
		if (typeof(this.items[in_key]) != 'undefined')
		{
			this.length--;
			tmp_value = this.items[in_key];
			delete this.items[in_key];
		}

		return tmp_value;
	}

	this.set = function(in_key, in_value)
	{
		if (typeof(in_value) != 'undefined')
		{
			if (typeof(this.items[in_key]) == 'undefined')
			{
				this.length++;
			}

			return this.items[in_key] = in_value;
		}

		return false;
	}

	this.has = function(in_key)
	{
		return typeof(this.items[in_key]) != 'undefined';
	}
}

// }}}
// {{{ domTT_callTimeout()

function domTT_callTimeout(in_function, in_timeout, in_args)
{
	if (!in_timeout)
	{
		in_function(in_args);
		return 0;
	}

	// must make a copy of the arguments so that we release the reference
	var tmp_args = new Array();
	for (var i = 0 ; i < in_args.length; i++)
	{
		tmp_args[i] = in_args[i];
	}

	if (!domTT_isKonq) {
		return setTimeout(function() { in_function(tmp_args); }, in_timeout);
	}
	else
	{
		var tmp_id = domTT_timeoutState.length;
		var tmp_data = new Array();
		tmp_data['function'] = in_function;
		tmp_data['args'] = tmp_args;
		domTT_timeoutState.set(tmp_id, tmp_data);

		domTT_timeoutState.items[tmp_id]['timeout'] = setTimeout("domTT_timeoutState.items[" + tmp_id + "]['function'](domTT_timeoutState.items[" + tmp_id + "]['args']); domTT_timeoutState.set(" + tmp_id + ", false);", in_timeout);
		return domTT_timeoutState.items[tmp_id]['timeout'];
	} 
}

// }}}
// {{{ domTT_mouseout()

function domTT_mouseout(in_triggerObj, in_this, in_event, in_tipId)
{
	if (typeof(in_this) != 'undefined')
	{
		var triggerObj = domTT_isIE || !in_triggerObj ? in_this : in_event.currentTarget;
		var eventRelatedTarget = domTT_isIE ? event.toElement : in_event.relatedTarget;
		var tipObj = domTT_isActive(in_tipId);
		// deactive tip if not yet active or if target we are entering is not 
		// a descendant of the trigger element
		if (!tipObj || !domTT_isDescendantOf(eventRelatedTarget, triggerObj))
		{
			domTT_deactivate(in_tipId, tipObj);
		}
	}

	if (in_triggerObj && in_triggerObj.domTT_mouseoutSaved)
	{
		in_triggerObj.domTT_mouseoutRun = function(in_event)
		{ 
			// we have to do this so that the user can use event in the handler
			if (domTT_isGecko)
			{ 
				event = in_event; 
			} 

			in_triggerObj.domTT_mouseoutSaved(in_event);
		}

		in_triggerObj.domTT_mouseoutRun(in_event);
	}

	try
	{
		window.status = window.defaultStatus;
	}
	// no permissions
	catch(e) {}
}

// }}}
// {{{ domTT_activate()

function domTT_activate(in_this, in_event)
{
	if (!domTT_useLibrary) { return true; }

	// get the unified event object and necessary event variables
	// since IE does not support the currentTarget, we have to use the 'this' reference
	var eventObj = domTT_isIE ? event : in_event;
	var eventType = eventObj.type;
	// we have a non-mouse event, such as an onload
	if (!eventObj[domTT_eventTarget])
	{
		eventType = '';
		var triggerObj = document.body;
	}
	else
	{
		var triggerObj = domTT_isIE ? in_this : eventObj.currentTarget;
		// make sure we have nothing higher than document.body
		if (triggerObj.nodeType == 9 || typeof(triggerObj.nodeType) == 'undefined')
		{
			triggerObj = document.body;
		}
	}

	// preserve the original mouseout event for this target object since we are overwriting it
	if (!triggerObj.getAttribute('domTT_mouseoutChecked') && typeof(triggerObj.domTT_mouseoutSaved) == 'undefined')
	{
		triggerObj.domTT_mouseoutSaved = triggerObj.onmouseout;
		triggerObj.setAttribute('domTT_mouseoutChecked', 1);
	}

	var activeTip = triggerObj.getAttribute('domTT_activeTip');

	// if the tip is already active and this is a mouseover, don't react
	if (eventType == 'mouseover' && activeTip == 'greasy')
	{
		return true;
	}

	// setup the options hash from the arguments
	var options = new domTT_Hash(
		'caption',		'',
		'content',		'',
		'closeLink',	domTT_closeLink,
		'parent',		document.body,
		'position',		'absolute',
		'type',			'greasy',
		'direction',	domTT_direction,
		'delay',		domTT_activateDelay,
		'prefix',		domTT_prefix,
		'onClose',		domTT_onClose,
		'lifetime',		domTT_lifetime,
		'grid',			domTT_grid,
		'fade',			domTT_fade
	);

	// load in the predefined options if specified
	if (arguments[2] == 'predefined' && domTT_predefined.has(arguments[3]))
	{
		var predefinedOptions = domTT_predefined.items[arguments[3]];
		for (var i in predefinedOptions.items)
		{
			options.set(i, predefinedOptions.items[i]);
		}
	}

	// load in the options from the function call
	for (var i = 2; i < arguments.length; i += 2)
	{
		options.set(arguments[i], arguments[i + 1]);
	}

	// add the event items into the hash
	options.set('triggerObj', triggerObj);
	options.set('eventType', eventType);

	// don't do anything if sticky tip is active and this is not a sticky tip
	// or if this is a greasy tip and another tip (like velcro) is active
	if ((activeTip == 'sticky' && options.items['type'] != 'sticky') ||
		(activeTip && activeTip != 'greasy' && options.items['type'] == 'greasy'))
	{
		return true;
	}

	// immediately set the status text if provided
	if (options.has('status')) {
		try
		{
			window.status = options.items['status'];
		}
		// no permissions
		catch(e) {}
	}

	// if we didn't give content...assume we just wanted to change the status and return
	if (options.items['content'] == '')
	{
		triggerObj.onmouseout = function() { domTT_mouseout(triggerObj); };
		return true;
	}

	// make sure we have a unique id, and handle case when there isn't one
	var tmp_uniqueId;
	if (!options.has('id'))
	{
		if (!(tmp_uniqueId = triggerObj.id))
		{
			triggerObj.id = tmp_uniqueId = 'domTT__id' + domTT_autoId++;
		}
	}
	else
	{
		tmp_uniqueId = options.items['id'];
	}

	// set the id option in the hash which will be used to label our tip
	var tmp_prefix = 'domTT:' + options.items['type'] + ':';
	options.set('id', tmp_prefix + tmp_uniqueId);

	// check for a cached tooltips on this object (can't have two at a time)
	var tipIsActive = (activeTip == options.items['type']);
	var tipObj = document.getElementById(options.items['id']);
	var altTipIsActive = (!tipIsActive && activeTip);
	var altTipId = altTipIsActive ? 'domTT:' + activeTip + ':' + tmp_uniqueId : false;

	// if there is a delay to create, we have to make sure we cancel that tip creation
	// if we mouseout before it happens **this must be here**
	triggerObj.onmouseout = function(in_event) { domTT_mouseout(triggerObj, this, in_event, options.items['id']); };

	// set the update delay, which is 0 if tip exists or this is a mouse button event
	options.items['delay'] = (tipIsActive || eventType.match(/click|mousedown|contextmenu/i)) ? 0 : parseInt(options.items['delay']);

	// get the mouse x and y coordinates as the bottom right edge of target for Konq
	var eventPositionSet = false;
	if (options.items['position'] == 'absolute')
	{
		if (options.has('x') && options.has('y'))
		{
			eventPositionSet = true;
			var mouse_x = parseInt(options.items['x']);
			var mouse_y = parseInt(options.items['y']);
		}
		// get the event location unless mouseover, then later
		// ** the delay will always be 0 for konqueror **
		else if (options.items['delay'] == 0 || !domTT_useGlobalMousePosition)
		{
			var eventPosition = domTT_getEventPosition(eventObj);
			eventPositionSet = true;
			var mouse_x = eventPosition.items['x'];
			var mouse_y = eventPosition.items['y'];
		}
	}
	// we don't need a mouse_x, mouse_y for relative positioning
	else
	{
		eventPositionSet = true;
		var mouse_x;
		var mouse_y;
	}

	// {{{ create tip if not exists

	// either there is no tooltip for this id, or we are changing our tooltip type
	// and we are going to need to recreate the tooltip with the new type
	if (!tipObj)
	{
		// we check if tipObj already exists and hide it in the case where we are changing type
		clearTimeout(domTT_activateTimeouts.items[options.items['id']]);
		domTT_activateTimeouts.set(options.items['id'], domTT_callTimeout(function(argv) { 
			// destroy alt tip (greasy if this is sticky)
			domTT_deactivate(argv[4]);
			// if this is a delay, get the current mouse position
			if (!argv[0])
			{
				argv[1] = domTT_currentMousePosition.items['x'];
				argv[2] = domTT_currentMousePosition.items['y'];
			}
			domTT_create(argv[1], argv[2], argv[3]); 
		}, options.items['delay'], new Array(eventPositionSet, mouse_x, mouse_y, options, altTipId)));

		return options.items['id'];
	}

	// }}}
	// {{{ show tip if exists

	clearTimeout(domTT_activateTimeouts.items[options.items['id']]);
	domTT_activateTimeouts.set(options.items['id'], domTT_callTimeout(function(argv) {
		domTT_deactivate(argv[4]);
		// if this is a delay, get the coordinates after the delay
		if (!argv[0])
		{
			argv[1] = domTT_currentMousePosition.items['x'];
			argv[2] = domTT_currentMousePosition.items['y'];
		}
		domTT_show(argv[1], argv[2], argv[3], argv[5], argv[6]);
	}, options.items['delay'], new Array(eventPositionSet, mouse_x, mouse_y, options, altTipId, tipObj, tipIsActive)));

	return options.items['id'];

	// }}}
}

// }}}
// {{{ domTT_create()

function domTT_create(in_x, in_y, in_options)
{
	var tipObj = document.createElement('div');
	tipObj.className = in_options.items['prefix'];
	tipObj.id = in_options.items['id'];
	tipObj.style.position = in_options.items['position'];
	// hide the tooltip so that we can get dimensions off of it without showing it
	tipObj.style.visibility = 'hidden';
	// give tooltip plenty of room to render if we are absolutely placing it
	if (in_options.items['position'] == 'absolute') {
		tipObj.style.left = 0;
		tipObj.style.top = 0;
	}

	tipObj.setAttribute('domTT_onClose', in_options.items['onClose']);
	tipObj.setAttribute('domTT_eventX', in_x);
	tipObj.setAttribute('domTT_eventY', in_y);
	tipObj.setAttribute('domTT_fade', in_options.items['fade']);
	tipObj.setAttribute('domTT_triggerId', in_options.items['triggerObj'].id);
	if (in_options.items['caption'] || (in_options.items['type'] == 'sticky' && in_options.items['caption'] !== false))
	{
		var tmp_useTitleBar = true;
		var tmp_captionBox = document.createElement('div');
		tmp_captionBox.className = in_options.items['prefix'] + 'Caption';
		var tmp_caption = document.createElement('span');
		if (in_options.items['type'] == 'sticky')
		{
			tmp_caption.style[domTT_cssFloat] = 'left';
		}

		tmp_caption.appendChild(document.createTextNode(in_options.items['caption']));
		tmp_captionBox.appendChild(tmp_caption);
		if (in_options.items['type'] == 'sticky') {
			tmp_close = document.createElement('span');
			if (!domTT_isIE)
			{
				tmp_close.style[domTT_cssFloat] = 'right';
			}

			tmp_close.style.cursor = domTT_stylePointer;
			if (typeof(in_options.items['closeLink']) == 'string') {
				tmp_close.innerHTML = in_options.items['closeLink'];
			}
			else
			{
				tmp_close.appendChild(in_options.items['closeLink'].cloneNode(1));
			}

			tmp_close = tmp_captionBox.appendChild(tmp_close);
			// this will get lost in the IE hack, so don't waste time here
			if (!domTT_isIE)
			{
				tmp_close.onclick = function() { domTT_deactivate(in_options.items['id'], tipObj); };
				tmp_close.onmousedown = function(in_event) { in_event.cancelBubble = true; };
			}

			tmp_break = document.createElement('br');
			tmp_break.style.clear = 'both';
			tmp_captionBox.appendChild(tmp_break);
		}
		tipObj.appendChild(tmp_captionBox);
	}
	else
	{
		var tmp_useTitleBar = false;
	}

	var tmp_content = document.createElement('div');
	if (in_options.items['content'].nodeType)
	{
		tmp_content.appendChild(in_options.items['content']);
	}
	else
	{
		tmp_content.innerHTML = in_options.items['content'];
	}

	tmp_content.className = in_options.items['prefix'] + 'Content';
	tipObj.appendChild(tmp_content);
	tipObj = in_options.items['parent'].appendChild(tipObj);

	// {{{ IE workarounds for float

	// ** nasty hack for IE to get the float working right **
	if (domTT_isIE && tmp_useTitleBar)
	{
		var tmp_oldWidth = tipObj.offsetWidth;
		// standards compliance, we must account for the width of the borders
		if (!domTT_isIE5)
		{
			tmp_oldWidth -= parseInt(tipObj.currentStyle.borderLeftWidth) + parseInt(tipObj.currentStyle.borderRightWidth);
		}

		if (in_options.items['type'] == 'sticky')
		{
			tipObj.firstChild.firstChild.nextSibling.style[domTT_cssFloat] = 'right';
		}

		tipObj.innerHTML = tipObj.innerHTML;
		tipObj.style.width = tmp_oldWidth + 'px';
		if (in_options.items['type'] == 'sticky')
		{
			var tmp_close = tipObj.firstChild.firstChild.nextSibling;
			tmp_close.onclick = function() { domTT_deactivate(in_options.items['id'], tipObj); };
			tmp_close.onmousedown = function() { event.cancelBubble = true; };
		}
	}

	// }}}

	// adjust the width if specified
	if (in_options.has('width'))
	{
		tipObj.style.width = parseInt(in_options.items['width']) + 'px';
	}

	// check if we are overridding the maxWidth
	// if the browser supports maxWidth, the global setting will be ignored (assume stylesheet)
	var tmp_maxWidth = domTT_maxWidth;
	if (in_options.has('maxWidth'))
	{
		if ((tmp_maxWidth = in_options.items['maxWidth']) === false)
		{
			tipObj.style.maxWidth = domTT_styleNoMaxWidth;
		}
		else
		{
			tipObj.style.maxWidth = tmp_maxWidth = parseInt(in_options.items['maxWidth']) + 'px';
		}
	}

	// ** fix lack of maxWidth in CSS for Konq and IE **
	if (tmp_maxWidth !== false && (domTT_isIE || domTT_isKonq) && tipObj.offsetWidth > tmp_maxWidth)
	{
		tipObj.style.width = tmp_maxWidth + 'px';
	}

	// ** hack for IE to make the tip object have a 'layout' so it can fade **
	if (domTT_isIE && in_options.items['position'] == 'relative' && in_options.items['fade'] != 'neither')
	{
		tipObj.style.height = (tipObj.offsetHeight - parseInt(tipObj.currentStyle.borderTopWidth) - parseInt(tipObj.currentStyle.borderBottomWidth)) + 'px';
	}

	if (in_options.items['type'] == 'sticky')
	{
		if (in_options.items['position'] == 'absolute' && domTT_dragStickyTips)
		{
			if (domTT_isIE)
			{
				tipObj.firstChild.onselectstart = function() { return false; };
			}

			// setup drag
			tipObj.firstChild.onmousedown = function(in_event) { domTT_dragStart(this, in_event);  };
			tipObj.firstChild.onmousemove = function(in_event) { domTT_dragUpdate(in_event); };
			tipObj.firstChild.onmouseup = function(in_event) { domTT_dragStop(in_event); };
		}
	}
	else if (in_options.items['type'] == 'velcro')
	{
		tipObj.onmouseout = function(in_event) { domTT_mouseout(false, tipObj, in_event, in_options.items['id']); };
	}

	domTT_show(in_x, in_y, in_options, tipObj, false);
}

// }}}
// {{{ domTT_show()

function domTT_show(in_x, in_y, in_options, in_tipObj, in_active)
{
	// if the tip is visible and we are using the grid, then make sure we have moved to
	// the next grid slot before we move the tooltip
	if (!in_active || !in_options.items['grid'] || Math.abs(in_tipObj.getAttribute('domTT_eventX') - in_x) > in_options.items['grid'] || Math.abs(in_tipObj.getAttribute('domTT_eventY') - in_y) > in_options.items['grid'])
	{
		in_tipObj.setAttribute('domTT_eventX', in_x);
		in_tipObj.setAttribute('domTT_eventY', in_y);
	}
	else
	{
		return;
	}

	// increase the tip zIndex so it goes over previously shown tips
	in_tipObj.style.zIndex = domTT_zIndex++;

	// if not yet active, we need to grab dimensions off the tooltip without showing it
	if (!in_active)
	{
		in_tipObj.style.visibility = 'hidden';
		in_tipObj.style.display = '';
	}

	// ** sometimes the offsetWidth/offsetHeight in konqueror are undefined **
	if (in_options.items['position'] == 'absolute')
	{
		// place the tip in the direction relative to the pointer
		switch (in_options.items['direction']) {
			case 'northeast':
				var tip_x = in_x + domTT_offsetX;
				var tip_y = in_y - in_tipObj.offsetHeight - domTT_offsetY;
			break;
			case 'northwest':
				var tip_x = in_x - in_tipObj.offsetWidth - domTT_offsetX;
				var tip_y = in_y - in_tipObj.offsetHeight - domTT_offsetY;
			break;
			case 'southwest':
				var tip_x = in_x - in_tipObj.offsetWidth - domTT_offsetX;
				var tip_y = in_y + domTT_mouseHeight + domTT_offsetY;
			break;
			case 'southeast':
				var tip_x = in_x + domTT_offsetX;
				var tip_y = in_y + domTT_mouseHeight + domTT_offsetY;
			break;
		}

		var tipCoordinates = domTT_correctEdgeBleed(in_tipObj.offsetWidth, in_tipObj.offsetHeight, tip_x, tip_y, domTT_offsetX, domTT_offsetY, in_options.items['type']);

		// update the position
		in_tipObj.style.left = tipCoordinates[0] + 'px';
		in_tipObj.style.top = tipCoordinates[1] + 'px';
	}

	// if tip is not active, active it now and check for a fade in
	if (!in_active)
	{
		in_options.items['triggerObj'].setAttribute('domTT_activeTip', in_options.items['type']);
		// unhide the tooltip
		in_tipObj.style.visibility = 'visible';

		// :QUESTION: what happens if it is fading in and an event fires again?
		// check to see if we are fading the tip
		if (domTT_isIE || domTT_isGecko)
		{
			var tmp_fade = in_options.items['fade'];
			if (tmp_fade == 'out' || tmp_fade == 'both')
			{
				clearTimeout(domTT_fadeTimeouts.items[in_options.items['id']]);
			}

			if (tmp_fade == 'in' || tmp_fade == 'both')
			{
				if (!in_active)
				{
					in_tipObj.style[domTT_styleOpacity] = domTT_isIE ? 'alpha(opacity=0)' : '0%';
				}

				domTT_fadeTimeouts.set(in_options.items['id'], setTimeout(function() { domTT_doFade(in_tipObj, 'in'); }, domTT_fadeInterval));
			}
			else
			{
				in_tipObj.style[domTT_styleOpacity] = domTT_isIE ? 'alpha(opacity=100)' : '100%';
			}
		}
	}

	// update the mouseout for sticky tips
	if (in_options.items['type'] == 'sticky')
	{
		in_options.items['triggerObj'].onmouseout = function() { domTT_mouseout(in_options.items['triggerObj']); };
	}
	else if (in_options.items['type'] == 'velcro')
	{
		in_options.items['triggerObj'].onmouseout = function() { domTT_mouseout(in_options.items['triggerObj']); };
	}
	else
	{
		in_options.items['triggerObj'].onmouseout = function(in_event) { domTT_mouseout(in_options.items['triggerObj'], this, in_event, in_options.items['id']); };
		// set the tip lifetime
		if (in_options.items['lifetime'])
		{
			clearTimeout(in_tipObj.getAttribute('domTT_lifetime'));
			in_tipObj.setAttribute('domTT_lifetime', domTT_callTimeout(function(argv) { domTT_deactivate(argv[0], argv[1]); }, in_options.items['lifetime'], new Array(in_options.items['id'], in_tipObj)));
		}
	}

	if (in_options.items['position'] == 'absolute')
	{
		domTT_detectCollisions(in_tipObj);
	}
}

// }}}
// {{{ domTT_doFade()

function domTT_doFade(in_object, in_direction)
{
	var matches = in_object.style[domTT_styleOpacity].match(/[0-9]+/);
	var opacity = new Number(matches[0]);
	if (in_direction == 'in')
	{
		if (opacity < 100)
		{
			var nextOpacity = Math.min(100, opacity + 10);
		}
		else
		{
			return;
		}
	}
	else
	{
		if (opacity > 0)
		{
			var nextOpacity = Math.max(0, opacity - 10);
		}
		else
		{
			in_object.style.display = 'none';
			return;
		}
	}

	in_object.style[domTT_styleOpacity] = domTT_isIE ? 'alpha(opacity=' + nextOpacity + ')' : nextOpacity + '%';
	domTT_fadeTimeouts.set(in_object.id, setTimeout(function() { domTT_doFade(in_object, in_direction); }, domTT_fadeInterval));
}

// }}}
// {{{ domTT_deactivate()

function domTT_deactivate(in_tipId, in_tipObj)
{
	if (!in_tipId)
	{
		return;
	}

	// cancel the creation of this tip if it is still pending
	// ** in konqueror, cancelling timeout id that doesn't exist freezes everything **
	if (domTT_isKonq)
	{
		// :WARNING: assuming no quotes here
		setTimeout('clearTimeout(domTT_activateTimeouts.items[\'' + in_tipId + '\']);', 0);
	}
	else
	{
		clearTimeout(domTT_activateTimeouts.items[in_tipId]);
	}

	var tipObj = in_tipObj ? in_tipObj : domTT_isActive(in_tipId);
	if (tipObj)
	{

		// :HACK: disable IFRAME content in opera, since it won't hide it
		if (domTT_isOpera && tipObj.lastChild.firstChild.tagName == 'IFRAME')
		{
			tipObj.style.top = domTT_hidePosition;
		}

		// clear the activeTip type if triggerObj reference is used
		var triggerObj = document.getElementById(tipObj.getAttribute('domTT_triggerId'))
		if (triggerObj)
		{
			triggerObj.setAttribute('domTT_activeTip', '');
		}

		if (tipObj.getAttribute('domTT_onClose') == 'hide')
		{
			if ((domTT_isIE || domTT_isGecko) && (tipObj.getAttribute('domTT_fade') == 'out' || tipObj.getAttribute('domTT_fade') == 'both'))
			{
				clearTimeout(domTT_fadeTimeouts.items[tipObj.id]);
				domTT_fadeTimeouts.set(tipObj.id, setTimeout(function() { domTT_doFade(tipObj, 'out'); }, domTT_fadeInterval));
			}
			else
			{
				tipObj.style.display = 'none';
			}
		}
		else
		{
			// guard against a fluke here
			if (tipObj.parentNode)
			{
				tipObj.parentNode.removeChild(tipObj);
			}
		}

		// unhide all of the selects that are owned by this object
		domTT_detectCollisions(tipObj, true); 
	}
}

// }}}
// {{{ domTT_isActive()

function domTT_isActive(in_id)
{
	if (!in_id)
	{
		return false;
	}

	var tooltipObj = typeof(in_id) == 'object' ? in_id : document.getElementById(in_id);
	if (tooltipObj && tooltipObj.style.display != 'none')
	{
		return tooltipObj;
	}
	else
	{
		return false;
	}
}

// }}}
// {{{ domTT_addPredefined()

function domTT_addPredefined(in_id)
{
	var options = new domTT_Hash();
	for (var i = 1; i < arguments.length; i += 2)
	{
		options.set(arguments[i], arguments[i + 1]);
	}

	// :TODO: use createContextualFrament for mozilla here (document.body has to exist)
	domTT_predefined.set(in_id, options);
}

// }}}
// {{{ domTT_correctEdgeBleed()

function domTT_correctEdgeBleed(in_width, in_height, in_x, in_y, in_offsetX, in_offsetY, in_type)
{
	var bleedRight;
	var bleedBottom;
	// for IE in compliance mode, maybe others
	if (document.documentElement.clientHeight)
	{
		var pageHeight = document.documentElement.clientHeight;
		var pageWidth = document.documentElement.clientWidth;
		var pageYOffset = document.documentElement.scrollTop;
		var pageXOffset = document.documentElement.scrollLeft;
	}
	else
	{
		var pageWidth = document.body.clientWidth;
		var pageYOffset = window.pageYOffset;
		var pageXOffset = window.pageXOffset;
		if (domTT_isKonq)
		{
			var pageHeight = window.innerHeight;
		}
		else
		{
			var pageHeight = document.body.clientHeight;
		}
	}

	// we are bleeding off the right, move tip over to stay on page
	if ((bleedRight = (in_x - pageXOffset) + in_width - (pageWidth - domTT_screenEdgePadding)) > 0)
	{
		in_x -= bleedRight;
	}

	// we are bleeding to the left, move tip over to stay on page
	// we don't want an 'else if' here, because if it doesn't fit we will bleed off the right
	if ((in_x - pageXOffset) < domTT_screenEdgePadding)
	{
		in_x = domTT_screenEdgePadding + pageXOffset;
	}

	// ** top/bottom corrections depends on type, because we can't end up with the mouse over
	// the tip if this is a greasy **
	// if we are bleeding off the bottom, flip to north
	if ((bleedBottom = (in_y - pageYOffset) + in_height - (pageHeight - domTT_screenEdgePadding)) > 0) {
		if (in_type == 'sticky') {
			in_y -= bleedBottom;
		}
		else
		{
			in_y -= in_height + (2 * in_offsetY) + domTT_mouseHeight;
		}
	}

	// if we are bleeding off the top, flip to south
	// we don't want an 'else if' here, because if we just can't fit it, bleed off the bottom
	if ((in_y - pageYOffset) < domTT_screenEdgePadding)
	{
		if (in_type == 'sticky')
		{
			in_y = domTT_screenEdgePadding + pageYOffset;
		}
		else
		{
			in_y += in_height + (2 * in_offsetY) + domTT_mouseHeight;
		}
	}

	return new Array(in_x, in_y);
}

// }}}
// {{{ domTT_detectCollisions()

function domTT_detectCollisions(in_tipObj, in_recover)
{
	// no need to do anything for opera
	if (domTT_isOpera)
	{
		return;
	}

	if (typeof(domTT_selectElements) == 'undefined')
	{
		domTT_selectElements = document.getElementsByTagName('select');
	}

	// if we don't have a tip, then unhide selects
	if (in_recover)
	{
		for (var cnt = 0; cnt < domTT_selectElements.length; cnt++)
		{
			var thisSelect = domTT_selectElements[cnt];

			// if this is mozilla and it is a regular select or it is multiple and the
			// size is not set, then we don't need to unhide
			if (domTT_isGecko && (!thisSelect.multiple || thisSelect.size < 0))
			{
				continue;
			}

			thisSelect.hideList.remove(in_tipObj.id);
			if (!thisSelect.hideList.length)
			{
				domTT_selectElements[cnt].style.visibility = 'visible';
			}
		}

		return;
	}

	// okay, we have a tip, so hunt and destroy
	var tipOffsets = domTT_getOffsets(in_tipObj);

	for (var cnt = 0; cnt < domTT_selectElements.length; cnt++)
	{
		var thisSelect = domTT_selectElements[cnt];

		// if this is mozilla and not a multiple-select or the multiple select size
		// is not defined, then continue since mozilla does not have an issue
		if (domTT_isGecko && (!thisSelect.multiple || thisSelect.size < 0))
		{
			continue;
		}

		// if the select is in the tip, then skip it
		// :WARNING: is this too costly?
		if (domTT_isDescendantOf(thisSelect, in_tipObj))
		{
			continue;
		}

		if (!thisSelect.hideList)
		{
			thisSelect.hideList = new domTT_Hash();
		}

		var selectOffsets = domTT_getOffsets(thisSelect); 
		// for mozilla we only have to worry about the scrollbar itself
		if (domTT_isGecko)
		{
			selectOffsets.set('left', selectOffsets.items['left'] + thisSelect.offsetWidth - domTT_scrollbarWidth);
			selectOffsets.set('leftCenter', selectOffsets.items['left'] + domTT_scrollbarWidth/2);
			selectOffsets.set('radius', Math.max(thisSelect.offsetHeight, domTT_scrollbarWidth/2));
		}

		var center2centerDistance = Math.sqrt(Math.pow(selectOffsets.items['leftCenter'] - tipOffsets.items['leftCenter'], 2) + Math.pow(selectOffsets.items['topCenter'] - tipOffsets.items['topCenter'], 2));
		var radiusSum = selectOffsets.items['radius'] + tipOffsets.items['radius'];
		// the encompassing circles are overlapping, get in for a closer look
		if (center2centerDistance < radiusSum)
		{
			// tip is left of select
			if ((tipOffsets.items['leftCenter'] <= selectOffsets.items['leftCenter'] && tipOffsets.items['right'] < selectOffsets.items['left']) ||
			// tip is right of select
				(tipOffsets.items['leftCenter'] > selectOffsets.items['leftCenter'] && tipOffsets.items['left'] > selectOffsets.items['right']) ||
			// tip is above select
				(tipOffsets.items['topCenter'] <= selectOffsets.items['topCenter'] && tipOffsets.items['bottom'] < selectOffsets.items['top']) ||
			// tip is below select
				(tipOffsets.items['topCenter'] > selectOffsets.items['topCenter'] && tipOffsets.items['top'] > selectOffsets.items['bottom']))
			{
				thisSelect.hideList.remove(in_tipObj.id);
				if (!thisSelect.hideList.length)
				{
					thisSelect.style.visibility = 'visible';
				}
			}
			else
			{
				thisSelect.hideList.set(in_tipObj.id, true);
				thisSelect.style.visibility = 'hidden';
			}
		}
	}
}

// }}}
// {{{ domTT_getOffsets()

function domTT_getOffsets(in_object)
{
	var originalObject = in_object;
	var originalWidth = in_object.offsetWidth;
	var originalHeight = in_object.offsetHeight;
	var offsetLeft = 0;
	var offsetTop = 0;

	while (in_object)
	{
		offsetLeft += in_object.offsetLeft;
		offsetTop += in_object.offsetTop;
		in_object = in_object.offsetParent;
	}

	return new domTT_Hash(
		'left',			offsetLeft,
		'top',			offsetTop,
		'right',		offsetLeft + originalWidth,
		'bottom',		offsetTop + originalHeight,
		'leftCenter',	offsetLeft + originalWidth/2,
		'topCenter',	offsetTop + originalHeight/2,
		'radius',		Math.max(originalWidth, originalHeight) 
	);
}

// }}}
// {{{ domTT_getEventPosition()

function domTT_getEventPosition(in_eventObj)
{
	var eventPosition = new domTT_Hash();
	if (domTT_isKonq)
	{
		eventPosition.set('x', in_eventObj.x);
		eventPosition.set('y', in_eventObj.y);
	}
	else if (domTT_isIE)
	{
		if (document.documentElement.clientHeight)
		{
			eventPosition.set('x', in_eventObj.clientX + document.documentElement.scrollLeft);
			eventPosition.set('y', in_eventObj.clientY + document.documentElement.scrollTop);
		}
		// :WARNING: consider case where document.body doesn't yet exist for IE
		else
		{
			eventPosition.set('x', in_eventObj.clientX + document.body.scrollLeft);
			eventPosition.set('y', in_eventObj.clientY + document.body.scrollTop);
		}
	}
	else
	{
		eventPosition.set('x', in_eventObj.pageX);
		eventPosition.set('y', in_eventObj.pageY);
	}

	return eventPosition;
}

// }}}
// {{{ domTT_isDescendantOf()

function domTT_isDescendantOf(in_object, in_ancestor)
{
	if (in_object == in_ancestor)
	{
		return true;
	}

	while (in_object != document.documentElement)
	{
		try
		{
			if ((tmp_object = in_object.offsetParent) && tmp_object == in_ancestor)
			{
				return true;
			}
			else if ((tmp_object = in_object.parentNode) == in_ancestor)
			{
				return true;
			}
			else
			{
				in_object = tmp_object;
			}
		}
		// in case we get some wierd error, just assume we haven't gone out yet
		catch(e)
		{
			return true;
		}
	}

	return false;
}

// }}}
// {{{ domTT_dragStart()

function domTT_dragStart(in_this, in_event)
{
	var eventObj = domTT_isIE ? event : in_event;
	var eventButton = eventObj[domTT_eventButton];
	if (eventButton != 1 && !domTT_isKonq)
	{
		return;
	}

	var eventTarget = domTT_isIE ? in_this : in_event.currentTarget;
	eventTarget = domTT_currentDragTarget = eventTarget.parentNode;
	eventTarget.style.cursor = 'move';

	// upgrade our z-index
	eventTarget.style.zIndex = ++domTT_zIndex;

	var eventPosition = domTT_getEventPosition(eventObj);

	var targetPosition = domTT_getOffsets(eventTarget);
	domTT_dragOffsetLeft = eventPosition.items['x'] - targetPosition.items['left'];
	domTT_dragOffsetTop = eventPosition.items['y'] - targetPosition.items['top'];
	domTT_dragMouseDown = true;
}

// }}}
// {{{ domTT_dragUpdate()

function domTT_dragUpdate(in_event)
{
	if (domTT_dragMouseDown)
	{
		if (domTT_isGecko)
		{
			window.getSelection().removeAllRanges()
		}

		var eventObj = domTT_isIE ? event : in_event;
		var eventTarget = domTT_currentDragTarget;
		var eventPosition = domTT_getEventPosition(eventObj);

		eventTarget.style.left = (eventPosition.items['x'] - domTT_dragOffsetLeft) + 'px';
		eventTarget.style.top = (eventPosition.items['y'] - domTT_dragOffsetTop) + 'px';

		// update the collision detection
		domTT_detectCollisions(eventTarget);
	}
}

// }}}
// {{{ domTT_dragStop()

function domTT_dragStop()
{
	if (domTT_dragMouseDown) {
		domTT_dragMouseDown = false; 
		domTT_currentDragTarget.style.cursor = 'default';
		domTT_currentDragTarget = null;
		if (domTT_isGecko)
		{
			window.getSelection().removeAllRanges()
		}
	}
}

// }}}
// {{{ domTT_true()

function domTT_true()
{
	return true;
}

// }}}
// {{{ domTT_false()

function domTT_false()
{
	return false;
}

// }}}
